/**
 * 
 */
package qy.jalgotrade.technical.ma;

import java.time.ZonedDateTime;

import org.apache.commons.math3.stat.StatUtils;

import qy.jalgotrade.dataseries.SequenceDataSeries;
import qy.jalgotrade.technical.EventBasedFilter;
import qy.jalgotrade.technical.EventWindow;

/**
 * Exponential Moving Average filter.
 * 
 * @author c-geo
 *
 */
public class EMA extends EventBasedFilter<Double> {

	/**
	 * 
	 * @author c-geo
	 *
	 */
	public static class EMAEventWindow extends EventWindow<Double> {

		private double __multiplier;

		private double __value;

		/**
		 * 
		 * @param period
		 */
		public EMAEventWindow(int period) {

			super(period, Double.class);
			assert period > 1;
			__multiplier = 2.0 / (period + 1);
			__value = Double.NaN;
		}

		/*
		 * (non-Javadoc)
		 * @see qy.jalgotrade.technical.EventBasedFilter.EventWindow#onNewValue(java.time.ZonedDateTime, java.lang.Object)
		 */
		@Override
		public void onNewValue(ZonedDateTime dateTime, Object value) {

			super.onNewValue(dateTime, value);

			// Formula from http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages
			if (value != null && windowFull()) {
				if (Double.isNaN(__value)) {

//					__value = StatUtils.mean(((DoubleList) getValues()).toDoubleArray());
					__value = StatUtils.mean((double[]) toPrimitiveArray());
				} else {
					__value = ((double) value - __value) * __multiplier + __value;
				}
			}
		}

		/*
		 * (non-Javadoc)
		 * @see qy.jalgotrade.technical.EventBasedFilter.EventWindow#getValue()
		 */
		@Override
		public Double getValue() {

			return __value;
		}
	}

	/**
	 * 
	 * @param dataSeries The DataSeries instance being filtered.
	 * @param period     The number of values to use to calculate the EMA. Must be an integer greater
	 *                   than 1.
	 * @param maxLen     The maximum number of values to hold. Once a bounded length is full, when new
	 *                   items are added, a corresponding number of items are discarded from the
	 *                   opposite end. If None then dataseries.DEFAULT_MAX_LEN is used.
	 * @throws Exception
	 */
	public EMA(SequenceDataSeries<Double> dataSeries, int period, int maxLen) throws Exception {

		super(maxLen);
		EMAEventWindow win = new EMAEventWindow(period);
		_init(dataSeries, win);
	}
}
